package net.apps.core.impl;

import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import net.apps.core.Addr;
import net.apps.core.ApplContext;
import net.apps.core.ApplManagerStd;
import net.apps.core.Channel;
import net.apps.core.ChannelListener;
import net.apps.core.Connection;
import net.apps.core.Message;
import net.apps.core.StdAppl;

/**
 * Application manager.
 * This class is instatiated by new incoming connection and application,
 * and controls lifecycle of an {@link StdAppl} instance. 
 * 
 * @author Maxim Kizub
 *
 */
public final class ApplManagerStdImpl implements ApplManagerStd {
	
	private static final int APPL_STATE_INITIAL = 0;
	private static final int APPL_STATE_CREATED = 1;
	private static final int APPL_STATE_RUNNING = 2;

	private final ArrayList<ApplState>     appls;
	private final ApplExecutor             appl_executor;
	
	private int[] apps_count;
	
	private class ApplExecutor extends ThreadPoolExecutor {
		ApplExecutor() {
			super(1, 1,0, TimeUnit.NANOSECONDS, new ArrayBlockingQueue<Runnable>(16));
		}

		protected void afterExecute(Runnable r, Throwable t) {
			super.afterExecute(r, t);
			if (t == null && r instanceof Future<?>) {
				try {
					Future<?> future = (Future<?>) r;
					if (future.isDone())
						future.get();
				} catch (CancellationException ce) {
					t = ce;
				} catch (ExecutionException ee) {
					t = ee.getCause();
				} catch (InterruptedException ie) {
					Thread.currentThread().interrupt(); // ignore/reset
				}
			}
			if (t != null)
				t.printStackTrace();
		}
	}
	
	private class ApplState {
		final Class<? extends StdAppl> appl_class;
		final StdConnectionImpl        appl_conn;
		final ApplContextImpl          appl_context;
		      StdAppl                  the_appl;
		      int                      curr_state;
		      int                      goal_state;
		 ApplState(Class<? extends StdAppl> appl_class) {
			this.appl_class = appl_class;
			this.appl_conn = new StdConnectionImpl();
			this.appl_context = new ApplContextImpl(ApplManagerStdImpl.this, appl_conn);
		}
	}
	
	private class RequestState implements Runnable {
		private StdAppl appl;
		private int goal_state;
		public RequestState(StdAppl appl, int goal_state) {
			this.appl = appl;
			this.goal_state = goal_state;
		}
		@Override
		public void run() {
			for (ApplState as : appls) {
				if (as.the_appl == appl) {
					as.goal_state = goal_state;
					ApplManagerStdImpl.this.loop_to_state(as);
					break;
				}
			}
		}
	}
	
	private class WakeupOnData implements Runnable {
		private Channel channel;
		public WakeupOnData(Channel channel) {
			this.channel = channel;
		}
		@Override
		public void run() {
			Connection conn = channel.getConnection();
			for (ApplState as : appls) {
				if (as.appl_conn == conn) {
					ApplManagerStdImpl.this.process_data(this, as, channel);
					break;
				}
			}
		}
	}
	
	private class WakeupOnAppl implements Runnable {
		private StdAppl appl;
		public WakeupOnAppl(StdAppl appl) {
			this.appl = appl;
		}
		@Override
		public void run() {
			for (ApplState as : appls) {
				if (as.the_appl == appl) {
					ApplManagerStdImpl.this.process_appl(this, as);
					break;
				}
			}
		}
	}
	
	public ApplManagerStdImpl() {
		this.appls = new ArrayList<ApplState>();
		this.appl_executor = new ApplExecutor();
		this.apps_count = new int[1];
	}
	
	void loop_to_state(ApplState as) {
		main_loop:
		for (;;) {
			if (as.curr_state == as.goal_state)
				return;
			switch (as.curr_state) {
			case APPL_STATE_INITIAL:
				if (as.goal_state > APPL_STATE_INITIAL) {
					try {
						as.the_appl = as.appl_class.getConstructor(ApplContext.class).newInstance(as.appl_context);
					} catch (Exception e) {
						e.printStackTrace();
						as.goal_state = APPL_STATE_INITIAL;
						continue main_loop;
					}
					as.curr_state = APPL_STATE_CREATED;
				}
				continue main_loop;
			case APPL_STATE_CREATED:
				if (as.goal_state > APPL_STATE_CREATED) {
					try {
						as.the_appl.onStart();
					} catch (Exception e) {
						e.printStackTrace();
						as.goal_state = APPL_STATE_CREATED;
						continue main_loop;
					}
					as.curr_state = APPL_STATE_RUNNING;
				} else {
					as.the_appl = null;
					as.curr_state = APPL_STATE_INITIAL;
					synchronized (apps_count) {
						apps_count[0] -= 1;
						if (apps_count[0] <= 0)
							apps_count.notifyAll();
					}
				}
				continue main_loop;
			case APPL_STATE_RUNNING:
				if (as.goal_state < APPL_STATE_RUNNING) {
					try {
						as.the_appl.onStop();
					} catch (Exception e) {
						e.printStackTrace();
					}
					as.curr_state = APPL_STATE_CREATED;
				}
				continue main_loop;
			}
		}
	}
	
	void process_data(WakeupOnData wup, ApplState as, Channel channel) {
		try {
			if (channel.peek() != null) {
				ChannelListener listener = channel.getChannelListener();
				if (listener != null)
					listener.onChannelEvent(channel);
			}
			if (channel.peek() != null)
				appl_executor.submit(wup);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	void process_appl(WakeupOnAppl wup, ApplState as) {
		try {
			as.the_appl.onWakeup();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void instantiateAppl(final Class<? extends StdAppl> appl_class) {
		synchronized (apps_count) {
			apps_count[0] += 1;
		}
		appl_executor.submit(new Runnable() {
			@Override public void run() {
				ApplState as = new ApplState(appl_class);
				ApplManagerStdImpl.this.appls.add(as);
				as.goal_state = APPL_STATE_RUNNING;
				loop_to_state(as);
			}
		});
	}
	
	public void requestFinish(StdAppl appl) {
		appl_executor.submit(new RequestState((StdAppl)appl, APPL_STATE_INITIAL));		
	}
	
	public void requestWakeup(StdAppl appl) {
		appl_executor.submit(new WakeupOnAppl(appl));		
	}
	
	public void run() {
		for(;;) {
			try {
				synchronized (apps_count) {
					if (apps_count[0] <= 0)
						return;
					apps_count.wait();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
				return;
			}
		}
	}
	
	class StdConnectionImpl implements Connection {
		
		private ChannelImpl channel;
		
		@Override
		public Addr getLocalAddr() {
			return null;
		}

		@Override
		public Addr getRemoteAddr() {
			return null;
		}

		@Override
		public Channel getChannel(int id) {
			if (id != 0)
				return null;
			return channel;
		}
		
		class ChannelImpl implements Channel {
			
			private final BlockingQueue<Message> inp = new ArrayBlockingQueue<Message>(16);
			private final BlockingQueue<Message> out = new ArrayBlockingQueue<Message>(16);
			private ChannelListener listener;
			
			@Override
			public Connection getConnection() {
				return StdConnectionImpl.this;
			}

			@Override
			public int getChannelId() {
				return 0;
			}

			@Override
			public ChannelListener getChannelListener() {
				return listener;
			}
			
			@Override
			public void setChannelListener(ChannelListener listener) {
				this.listener = listener;
			}

			@Override
			public boolean push(Message msg, long timeout) {
				boolean added = false;
				try {
					added = out.offer(msg, timeout, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return false;
				}
				appl_executor.submit(new WakeupOnData(channel));
				return added;
			}

			@Override
			public Message take(long timeout) {
				try {
					return inp.poll(timeout, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return null;
				}
			}

			@Override
			public Message peek() {
				return inp.peek();
			}

			@Override
			public void shutdown() {
			}
		}

	}
}
